Build 446 v089 CHANGELOG


- Seduce minimum attractiveness bypass (settings)

- Added "Sex gameplay mechanics" setting:
  - "SeduceIsFreeToUse" (default "true", UI: Enabled/Disabled)
- When "SeduceIsFreeToUse" is enabled:
  - "Seduce" interaction test skips the legacy gate that requires actor "Attractive >= 6" (or Attractive trait / Attractive moodlet) for both manual and autonomous eligibility;
  - all other Seduce tests, scoring, acceptance, and target-side "Rejected" logic are unchanged.
- Compatibility/migration:
  - default "true" for fresh installs ("ResetSettings()"), upgrades that lack the key ("Upgrade()" when "PreviousVersion < 446"), and integrity backfill ("EnsureReleaseRequiredSettingsPresent()");
  - if the key is already present in serialized settings, the stored value is preserved (players who explicitly set Disabled keep it).
- STBL (setting label):
  - "Oniki.KinkyMod.OptionSettings.SeduceIsFreeToUse"
  - "0x227AF22F80E167F6"


Build 446 v089 - Global Settings: Ignore Teens AgeScale default

- "IgnoreTeensAgeScale" (Global Settings → Teens interactions) now defaults to **enabled** ("true") in "ResetSettings()", "Upgrade()" add-if-missing ("PreviousVersion < 446"), and "EnsureReleaseRequiredSettingsPresent()".
- Serialized values are preserved when the key already exists.

Build 446 v089 - Runtime stability (WooHoo vaginal)

- Added a defensive fix in "Oniki.Utilities.WooHooTools.PostWooHoo_Vaginal(...)" for transient null "targetData.Womb" states.
- Before using womb-dependent logic ("IsFertile", "IsHavingPeriod", "AddSperm"), the flow now attempts an immediate self-heal via "targetData.UpdateGender()" when the target is female human teen+ and womb is missing.
- If womb remains unavailable after self-heal, the code now safely skips womb-dependent branches for that pass instead of throwing a "NullReferenceException".
- Added explicit runtime logging for this condition ("PostWooHoo_Vaginal: target womb is null ...") to support root-cause diagnosis of lifecycle desyncs.
- Scope: crash prevention and diagnostics only; no intentional gameplay rebalance to acceptance/autonomy logic.

Build 446 v089 - Towel after bath/shower compatibility

- Fixed towel post-bath outfit compatibility by expanding towel CASP name candidate resolution in "SwitchOutfitHelper".
- The resolver now supports both naming layouts:
  - age-first: "<age><gender>_towel" (current logic)
  - gender-first: "<gender><age>_towel" (legacy package convention)
- Includes both standard age prefix and merged-adult age prefix variants to improve cross-package coverage.
- Updated "Sim_RepairTowelMapping" to use the same expanded candidate list, so manual repair follows runtime behavior exactly.
- Result: when "UseTowelAfterBath = true", towel lookup no longer fails silently on installs using legacy towel CASP naming.

Build 446 v089 - Services UX (Stray Pet Services)

- Added a non-modal localized warning popup when selecting "Services -> Stray Pet Services" while "ZooLover" is disabled.
- Replaced low-visibility red warning toast with a proper localized popup dialog ("SimpleMessageDialog") aligned with the "Change Kinky Traits" incompatible-selection UX pattern.
- The row now explicitly reports "Enabled/Disabled" state again instead of showing ""..."", improving readability and reducing "click does nothing" confusion.
- New localized key used by the warning:
  - "Oniki.KinkyMod.OptionSettings.MenuStrayPetService.ZooLoverRequired"

Build 446 v089 - Camera Mode (experimental) underwater availability

- Updated "Camera Mode (Experimental)" interaction definitions to implement "IScubaDivingInteractionDefinition" for object/terrain/target-Sim click paths.
- Scope updated:
  - "CameraModeOnObject.Definition"
  - "CameraModeOnTerrain.Definition"
  - "CameraModeOnTargetSim.Definition"
- Result: camera-mode interaction can remain available in scuba-diving context (underwater interaction filtering no longer excludes these definitions by interface mismatch).

Build 446 v089 - Underwater menu availability (Settings + Kinky Traits)

- Added "IScubaDivingInteractionDefinition" to core menu entry interactions so they remain eligible in scuba-diving interaction context.
- Scope updated:
  - "ShowSettings.Definition"
  - "ShowSettingsOnObject.Definition"
  - "ShowSettingsOnTerrain.Definition"
  - "SelectKinkyTraits.Definition" ("Sim_SelectKinkyTraits")
- Runtime behavior is unchanged on land; this is an availability/visibility compatibility fix for underwater filtering only.

Build 446 v089 - Romantic bucket duplicate mitigation toggle (EA + KW)

- Added a new Miscellaneous setting:
  - "EnableKWRomanticSocialsReinjection" (default "false")
- Purpose:
  - allow players to disable KW romantic social reinjection in pie menu when EA romantic socials are already available, reducing duplicate visible entries (for example "Flirt", "Kiss", "Make Out"-family overlaps).
- Runtime behavior when set to "false":
  - "SocializeProxy" skips KW romantic reinjection branch in "ProxyDefinition.AddInteractions(...)";
  - KW no longer injects its romantic candidate arrays ("kFlirtyGreetings", "kFlirtySocials", "kAmorousSocials", "kKisses") and related romantic progression additions from that branch.
- Runtime behavior when set to "true":
  - current/legacy reinjection behavior is preserved.
- Compatibility/migration:
  - setting is migration-safe for existing saves ("add-if-missing") and initialized in reset defaults.

Build 446 v089 - Romantic reinjection toggle refinement (special-case compatibility)

- Refined "EnableKWRomanticSocialsReinjection" behavior in "Oniki.Interactions/SocializeProxy.cs" after validation of missing special-case socials when toggle was OFF.
- Updated policy:
  - toggle OFF still disables duplicate-prone KW romantic reinjection for normal vanilla-compatible adult paths;
  - but reinjection is now preserved for legacy-special paths that require KW-side gating support:
    - "YoungAdultOrAbove <-> Teen" when "Teens = true"
    - incest-compatible paths (step/blood) when allowed by incest level/scope rules.
- Incest scope coherence hardening:
  - "ProxyDefinition.Test(...)" incest branch now explicitly respects "SimTools.IsIncestScopeAllowed(...)";
  - behavior remains consistent with "IncestActiveHouseholdOnly" (household-limited incest mode no longer leaks through this social path).
- Practical result:
  - users can keep duplicate mitigation enabled without losing intended special-case romantic availability for YA/Teen and allowed incest contexts.

Build 446 v089 - Funny bucket rollback + mode toggle

- Reverted Funny bucket ownership to KW legacy-style multi-provider flow in "Oniki.Interactions/SocializeProxy.cs" (closer to legacy behavior than strict EA-only ownership).
- Restored Funny pipeline components in "ProxyDefinition.AddInteractions(...)":
  - "AddVanillaFunnySocials(...)"
  - "AddValidatedFunnyFallbacks(...)" (key-validated fallback injection)
  - "AddKinkyJokeIfMissing(...)" (safety inclusion pass)
- Restored legacy funny action remap:
  - "Tell Dirty Joke" -> "Tell Kinky Joke" via "SocializeInteraction.CreateAction(...)".
- Added new Misc setting:
  - "RestoreEAFunnyBucket" (default "false")
- "RestoreEAFunnyBucket = false" (default):
  - keeps the more legacy-like KW visible behavior (baseline similar to Kinky Light / edited 444 expectations).
- "RestoreEAFunnyBucket = true":
  - enables current multi-provider augmentation path for Funny (EA candidate pull + fallback key injection + KinkyJoke ensure pass).
- Scope note:
  - this toggle affects Funny bucket composition strategy only; it does not change romantic reinjection toggle semantics.

Build 446 v089 - Temporary post-sweep dedupe bypass (diagnostic)

- Temporarily disabled final post-aggregation normalize pass in "PostAggregationSweepDefinition.AddInteractions(...)":
  - "NormalizeResultsByActionKey(...)"
  - "NormalizeResultsByVisibleLabel(...)"
- Reason:
  - isolate runtime behavior and validate whether final dedupe stage was removing expected Funny entries (notably "Tell Kinky Joke" in incest/blood contexts).

Build 446 v089 - Non-vaginal CreampiedPenis extension toggle

- Added a new setting in "Sex gameplay mechanics":
  - "ExtendCreampiedPenisToNonVaginal" (default "true")
- Runtime behavior when enabled:
  - extends "CreampiedLocations.Penis" application to giver-side target logic in "PostWooHoo_Handjob(...)" and "PostWooHoo_Oraljob(...)";
  - relaxes anal gating in "PostWooHoo_Anal(...)" from strict "CreampiedAnal" dependency to:
    - "extend-toggle ON" OR "legacy CreampiedAnal present".
- Runtime behavior when disabled:
  - legacy behavior is preserved.
- Compatibility/migration:
  - default "true" for "ResetSettings()", "Upgrade()" ("PreviousVersion < 446") add-if-missing, and "EnsureReleaseRequiredSettingsPresent()";
  - existing serialized values are never overwritten.
- STBL key (setting):
  - "Oniki.KinkyMod.OptionSettings.ExtendCreampiedPenisToNonVaginal"

Build 446 v089 - Male climax moodlet toggle ("mClimax")

- Added a new setting in "Sex gameplay mechanics":
  - "EnableMaleClimaxMoodlet" (default "true")
- Added new buff id registration in code:
  - "KWBuff.mClimax" = "0x9C55EE6441D6F1EF" ("11265172157305909743")
- Added centralized climax application helper in "WooHooTools":
  - "AddConfiguredClimaxMoodlet(SimData, Origin)".
- Runtime behavior:
  - female path remains unchanged ("fClimax" / "fClimaxFertile");
  - male path is enabled only when toggle is ON and applies "mClimax";
  - safety fallback kept: if "mClimax" is unavailable at runtime, fallback to "fClimax".
- Current interaction scope:
  - handjob success branch;
  - oraljob success branch;
  - vaginal success branch ("outcome >= 1") for non-rape male target when toggle is ON.
- Resource binding:
  - "KinkyBuffs" XML updated with "mClimax" entry and "ThumbFilename = moodlet_creampiegrool" (existing IMAG resource).
- Compatibility/migration:
  - default "true" for "ResetSettings()", "Upgrade()" ("PreviousVersion < 446") add-if-missing, and "EnsureReleaseRequiredSettingsPresent()";
  - existing serialized values are never overwritten.
- STBL (new/required):
  - "Oniki.KinkyMod.OptionSettings.EnableMaleClimaxMoodlet"
  - "0xB83DCCA81B852849"
  - "mClimax"
  - "0x00BA34AD5C3548CA"
  - "mClimaxDescription"
  - "0xB9DDF1A5D26667C0"
  - "mClimaxHelpText"
  - "0x5D6FB2A6C1ADD7E6"

Build 446 v089 - Meta-autonomy None-pass satiety (buffet + prepared plate)

- **Context (session validation):** Autonomy debug export review confirmed the existing buffet block behaves as designed in "CommodityKind.None" when hunger motive is high without hungry moodlets: "GrabFavoriteFood@BuffetTable" is skipped with "none_pass_satiety_buffet_block" (threshold 50 on hunger motive). Observed that Sims can still pick a parallel EA path, "ServingContainerGroup_GrabAServing@PlateServing" (target "PlateServing"), which is not a "BuffetTable" and therefore was not covered by the buffet-only guard.
- **Change:** In "Oniki.Gameplay.KWAutonomy" ("FindBestInteraction" / commodity "None" branch, immediately after the buffet check), the same satiety rule now also rejects GrabAServing-style interactions on a "PlateServing" object when "Hunger" motive is above the same constant ("kNonePassBuffetSatietyHungerMin", 50) - aligned with the buffet fix goal (reduce opportunistic “grab more food” in meta-autonomy when already well fed).
- **Helper:** "IsGrabAServingOnPlateServingTarget(InteractionInstance)" - requires target runtime type name "PlateServing" and interaction type name containing "GrabAServing" (e.g. "ServingContainerGroup_GrabAServing@PlateServing").
- **Trace / anti-loop (distinct from buffet):** "reason=none_pass_satiety_prepared_serving_block"; "AddAutonomyAntiLoopCooldown(..., "none_pass_satiety_prepared_serving_block")". Buffet continues to use "none_pass_satiety_buffet_block" for both trace and cooldown so exports remain separable in logs.
- **Doc:** XML summary on the shared hunger-threshold constant updated to mention prepared plate GrabAServing in addition to buffet.
- **Build:** Touched "KWAutonomy.cs"; no new settings, STBL, or migration keys.

Build 446 v089 - Selectable player-input bypass (sleeping target on bed)

- **Setting (existing):** "SelectableSimsAlwaysAcceptPlayerInputs" - no new keys or UI.
- **Problem:** With "AutonomyLevel = High", player-directed interactions that use the **Actor** (PlumbBob Sim) to approach a **sleeping** Sim on a valid multi-part bed still ran NPC-style gates before continuing: low-corruption block + "WooHooTools.AcceptWooHooAndExhibition(...)" (or exhibition-only accept for masturbate variant), producing “won’t do that” style failure on the controlled Actor.
- **Change:** Mirror "WooHooJoinInShower" pattern:
  - "playerDirectedSelectableControl = !Autonomous && SelectableSimsAlwaysAcceptPlayerInputs && Actor.IsSelectable"
  - When true, skip the entire "!Autonomous && AutonomyLevel.High" pre-check block for that interaction.
- **Files:**
  - "Oniki.Interactions/WooHooSleepingSimOnBed.cs" - skips corruption + "AcceptWooHooAndExhibition(Actor, Target, ...)" for oral/handjob/vaginal-on-sleeper.
  - "Oniki.Interactions/MasturbateOnSleepingSimOnBed.cs" - skips the same exhibition/accept gate ("Teasing" pass) for the masturbate-on-sleeper path.
- **Not changed:** "LickSleepingSimOnBed" (no equivalent "Run()" gate). Later-stage logic inside "KWBedSleep" (wake-up, mutual "AcceptWooHoo", etc.) is unchanged.

Build 446 v089 - Global Settings: manual Kinky Traits spread (one-click)

- **Entry point:** first row in Global Settings - action "Spread Kinky Traits" with "..." display (action row, not a toggle).
- **Flow:** localized confirm ("Accept" / "Cancel") → 0–100 progress loader (injection-style) → report dialog with "Total Sims processed" and "Errors" counts.
- **UX parity with injection flow:** settings picker closes immediately after confirm; game speed forced to pause for the whole spread; pause held until the final report is dismissed; settings are not auto-reopened afterward.
- **Manual-only:** no startup/world-load spread; no "OnSimInstantiated" / "OnSimAgeTransition" hooks; spread runs only when the player clicks the action.
- **Persistence:** per-Sim marker "mKinkyTraitsSpreadInitialized" in "SimData" (serialized/import/export); Sims already marked are skipped on later runs.
- **Eligibility / non-invasive policy:** filtered scope (valid human teen+ NPCs, non-active household, sane data); baseline applied only when "visibleCount <= 0"; Sims that already have visible kinky traits are left unchanged.
- **Baseline assignment:** deterministic alternation by Sim id - target core count 1 or 2, preference count 2 (typically 3–4 visible traits), still subject to "MaxTraits" and conflict rules.
- **Reward traits:** excluded from spread core counting and from spread core candidate lists (baseline uses Core-appropriate picks only).
- **Localization:** spread dialogs support "{0.Number}" / "{1.Number}" style numeric placeholders; confirm uses standard Accept/Cancel dialog flow.
- **Investigate Kinky Traits (related):** relationship gate restored; missing relationship shows explicit modal guidance; true “no unknown left” path restored; relationship is validated **before** any LTR spend (no point transaction on missing-relationship branch).
- **Spread confirm reliability:** staged from picker via safe modal stop + async path; Cancel on spread confirm resumes settings; Accept nulls picker callbacks and closes the chain before progress/report to avoid auto-reopen races.
- **Physical Sim click:** "Sim_SelectKinkyTraits" "Test()" no longer hard-fails on "SimData == null" / volatile - pie entry aligned with relationship-panel scope (service Sims may have weaker persistence; not crash-oriented).
- **Trait taxonomy:** "Cheater" / "Unfaithful" reclassified from Preference to **Core** ("kCoreKinkyTraits" / "kPreferenceKinkyTraits" arrays); spread inherits this without extra spread-specific logic.
- **Trait-earned at cap (overflow UX):** for selectable Sims, deferred replace-popup flow takes priority; the parallel top-left “cap full” notify is suppressed when the replace popup is queued; cap-full notify remains fallback when a modal cannot be shown.
- **Design note:** the historical multi-setting “spread family” (EnableKinkyTraitsSpread, modes, etc.) was **not** shipped; shipping design is this single manual action only.

Build 446 v089 - Shower: user-directed masturbation + related join UX

- **Goal:** manual command while the Sim is already in KW "TakeShower" (normal shower / "IShowerable"), localizable, with gating aligned to autonomy and player-control settings.
- **"TakeShower.cs":**
  - "StartMasturbationInternal(...)" - shared path for autonomy and player-requested masturbation ("Masturbate" stage, public undress dialogs as before, "AnimateSim("Loop Masturbate")", stage timing).
  - Autonomy random branch calls "StartMasturbationInternal" instead of duplicated body.
  - "DuringShower" handles "mStageRequest == Masturbate" (arousal tick + "StartMasturbationInternal").
  - "EvaluateUserDirectedShowerMasturbation" / "TryRequestUserDirectedMasturbation" - pie "Test" vs runtime request.
- **New interaction:** "TakeShower_BeginMasturbation.cs" - immediate self-target interaction, injected via "InteractionInjector"; "Run()" resolves "CurrentInteraction as TakeShower" and calls "TryRequestUserDirectedMasturbation()".
- **Scope limit:** applies to "Oniki.Interactions.TakeShower" only - not "ShowerOutdoor_TakeShower" / "ShowerPublic_Dance_TakeShower" (separate classes).
- **Gender (manual):** males need unified **Submissive** unless "GenderFreeMasturbateOnAllFours" is ON (parity with Masturbate-on-all-fours policy); females allowed within "Evaluate" limits.
- **Arousal + selectable bypass:** when "SelectableSimsAlwaysAcceptPlayerInputs && Actor.IsSelectable", "TryRequest" does not require "IsAroused"; otherwise not aroused → no start + "UITools.FeedbackTalking" with "Oniki.KinkyMod.Interaction:GenericRejected" (visible pie entry; reject on click, not greyed tooltip).
- **Pie path:** "CommodityDefinition" + "Socializing.Kinky", then "Gameplay/Actors/Sim/WooHoo:InteractionName" + ellipsis (not "Kinky World…").
- **Underage:** "SimTools.IsUnderAge" → interaction absent from pie ("Test" / "Evaluate" false, no tooltip).
- **Silent hides (no grey tooltip):** KW off / missing "SimData", cold shower, non-"Shower" stage, active "mHavingWooHoo", male gender lock without Submissive/Gender Free - "Evaluate" returns false without "greyedOutTooltipCallback".
- **STBL (action name):** "Oniki.KinkyMod.TakeShower.BeginMasturbationInShower:InteractionName" (package must supply string; "GenericRejected" reused for arousal reject feedback).
- **"WooHooJoin.cs" ("StageAccepted"):** when "!autonomous && SelectableSimsAlwaysAcceptPlayerInputs && actor.IsSelectable && newSim != null" and the scored slot is the player-directed actor’s slot, skip NPC-style score rejection for that slot only (covers both inviter and player-as-third join flows; other participants unchanged).
- **Build:** "Oniki_KinkyMod.csproj" includes "TakeShower_BeginMasturbation.cs".

Build 446 v089 - Mermaid dermal hydration ("MermaidDermalHydration": autonomy + interactions)

**Player-facing goal:** mermaids recover **dermal hydration** in line with EA expectations - showers/baths (and equivalent hygiene actions) do not end **only** because "Hygiene" is full while hydration is still low; autonomy treats low hydration as a real need (without replacing pool **drowning/fatigue** emergency logic). Non-mermaid paths stay unchanged.

**Autonomy pipeline ("Oniki.Gameplay/AutonomyManager.cs"):**
- Mermaid motive integrated into critical-need detection, need-signature channel ("MermaidDermalHydration" severity separate from water-emergency), "TryGetNeedMoodletCommodity(...)" warning/critical thresholds, temporary need-suppression commodity list, and duplicate need token matching ("IsNeedLikeToken" / sponge-sink style tokens where applicable).
- Busy-override / preempt policy: critical **mermaid hydration** ordered before **sleep lock-in** ("Energy") where applicable; critical resolver hardened to pick the **most severe** critical motive among bladder/hunger/energy/vampire thirst/mermaid hydration; sleep hard-preempt fallback when a non-energy critical need must break sleep; queue-forensics kept for silent skip reasons ("interaction_queue_not_empty", duplicate busy-override, etc.).
- Hydration dispatch churn: throttle when hydration is already queued under lock; defer hydration preempt during **WooHoo-like** current actions; defer **Hunger** critical cancel against an in-progress **vital** current action when hydration has no enqueueable candidate (reduces cancel/retry loops); optional "KW-AUTONOMY-TRACE" instrumentation for need-busy-override phases ("TraceNeedBusyOverrideAutonomy").

**KW autonomy ("Oniki.Gameplay/KWAutonomy.cs"):**
- Parity hooks: mermaid motive in idle recovery candidacy, base need-commodity set, "HasNeedMoodletForCommodity", "GetGoHomeMotiveThreshold", "ApplyMoodletNeedOverrides", and need-candidate token matching ("IsLikelyNeedMatch").
- **Hydration candidate policy:** classifies candidates ("swim_like", "shower_bath_like", "water_misc", **"sink_like" rejected**); "TrySelectHydrationPreferredCandidate(...)" rescoring for "MermaidDermalHydration" with validation + anti-loop cooldowns; dedicated "FindBestInteraction(MermaidDermalHydration, ...)" branch (replace sink/null picks, go-home fallback when nothing valid); **cross-commodity guard** when hydration is critically low - reject sink-like picks even off the hydration branch, try swim/shower/bath-like rescoring, then go-home fallback.
- **Classifier ordering:** sink token classification ordered **before** generic "bath" substring matching so "Sink_SpongeBath" is not misclassified as shower/bath-like.
- **Fallback scoring pools:** if the primary hydration commodity pool starves valid candidates, additional scoring passes may use **"Hygiene"** and **"Fun"** tuning lanes while still enforcing hydration class filters (no sink-like).
- **Mermaid hunger lane:** when the actor has the mermaid motive and hunger is under pressure, prefer **inventory fish/kelp** eat interactions (validated tokens, not fishing/harvest) before generic quick-food fallback.

**Shower / bath / sink ("Oniki.Interactions"):**
- **"TakeShower.cs":** "GetShowerTime()" combines hygiene + hydration time targets (max-based); loop completion uses combined **hygiene + hydration** satisfaction for mermaids with timeout safety; minimum shower duration floor for mermaids; on exit, "MermaidDermalHydration" set to max when appropriate if not user/script canceled; helpers "HasMermaidHydrationMotive", "IsMermaidHydrationSatisfied", "ShouldFinishShowerLoop".
- **"BathTub_TakeBath.cs":** "GetBathTimeWithMermaidHydration()" extends bath duration when needed; on successful completion, sets max **Hygiene** and **MermaidDermalHydration** (when motive exists).
- **"Sink_SpongeBath.cs":** deterministic minimum sponge duration for mermaids when EA loop exits early; successful completion restores **MermaidDermalHydration** to max (aligned with autonomy policy that sink is a poor primary hydration autonomy target but still must work if chosen).
- **"ShowerOutdoor_TakeShower.cs" / "ShowerPublic_Dance_TakeShower.cs":** mermaid hydration max on exit preserved; **removed** risky post-loop timed extension that could fault the native shower state machine (crash stabilization - no second timed loop after base exit).

**EP10 all-in-one bathroom ("Oniki.Interactions/AllInOneBathroom_UseAllInOneBathroom.cs"):**
- Wraps EA "UseAllInOneBathroom"; on **successful** completion, if the actor has the mermaid hydration motive and the interaction was not user/script canceled, forces "Motives.SetMax(MermaidDermalHydration)" (EA path does not reliably restore hydration).

**Related stabilization (shared need-core policy):**
- "AutonomyManager" need-core defer / warning-preempt protection and hunger serving continuity changes documented in the DONE were partly driven by **hydration queue churn** and **sink** interactions appearing in need resolution; sponge/sink tokens are treated consistently where need-core “in progress” protection applies.

**Scope / non-goals:** water-emergency ("Drowning" / swim distress) remains a separate, higher-priority channel; no intentional change to non-mermaid shower/bath timing except as noted for outdoor/public crash-safe stabilization.

Build 446 v089 - Incest mechanics scope split (NPC vs active household)

**Goal:** two **independent** booleans so players can allow or block incest **mechanics** separately for (a) pairs involving the **active household** and (b) **NPC / outside-household** pairs. This is orthogonal to **"IncestLevel"** ("Disabled" / "StepRelated" / "BloodRelated"), which still defines **which relation types** count as incest.

**New settings (defaults "true" / migration-safe add-if-missing, preserve serialized values when keys exist):**
- "NPCsIncestMechanics" - when "true", incest mechanics may apply when **neither** Sim is resolved as active-household scope (townies / NPC context); when "false", that scope is blocked.
- "HouseholdIncestMechanics" - when "true", incest mechanics may apply when **at least one** Sim is in the active household; when "false", that scope is blocked.

**STBL (setting labels, package must supply hashes per language workflow):**
- "Oniki.KinkyMod.OptionSettings.NPCsIncestMechanics"
- "Oniki.KinkyMod.OptionSettings.HouseholdIncestMechanics"

**Legacy migration from "IncestActiveHouseholdOnly":** when the new keys are first introduced, "NPCsIncestMechanics" is initialized from the legacy flag: **household-only ON** (legacy "true") maps to **NPC scope OFF**; **household-only OFF** maps to **NPC scope ON**; "HouseholdIncestMechanics" defaults **true**. Existing saves keep explicit user values once keys exist.

**Central gate:** "SimTools.IsIncestScopeAllowed(Sim/IMiniSimDescription actor, target)" - active-household membership drives which boolean is consulted; "IncestLevel" and other incest checks remain unchanged. Call sites (manual autonomy, social/proxy filters, etc.) should route through this gate for consistent scope.

**UI:** Global Settings entries via "OptionSettingMenuGlobal.cs" ("HouseholdIncestMechanics", "NPCsIncestMechanics").

**Validation posture (from design doc):** matrix all four combinations of the two toggles under each "IncestLevel"; confirm no cross-scope leak. Tests reported: NPC-only and household-only modes behave as expected when enabled independently.

Build 446 v089 - KW premade NPC engine master switch + disposal notification control

**Goal:** a global **ON/OFF** for the KW premade NPC engine ("Oniki.Roles.NPC") so players can stop create/recreate/bootstrap behavior and suppress the recurring notify: "NPC.OnSimDescriptionDisposed: Npc has been destroyed: <Name>".

**"EnableKWPremadeNPCs" (default "true", backward-compatible):**
- **"Main.cs":** "NPC.Startup()" runs only when this setting is **ON**.
- **"Main.cs":** "OnSimDescriptionDisposed" handler calls "NPC.OnSimDescriptionDisposed" only when **ON** and the disposed description is a KW NPC.
- **"NPC.cs":** defensive early return in "OnSimDescriptionDisposed" when **OFF** so dispose-driven recreate paths cannot run.
- **UI:** Global Settings → Services ("OptionSettingMenuServices.cs"), "OptionSettingEnabledSimple" row (Enabled/Disabled display).

**STBL (label, package must supply hash per release workflow):**
- "Oniki.KinkyMod.OptionSettings.EnableKWPremadeNPCs"

**"NPCsDisposalNotification" (default "true"):**
- **Purpose:** notification-only kill-switch for the same disposal popup if an edge path still fires - does **not** change create/recreate logic.
- **"NPC.cs":** "UITools.Notify(...)" disposal branch guarded by "Main.Settings.GetBool("NPCsDisposalNotification")".
- **UI:** Dialogs & Notifications ("OptionSettingMenuDialogsNotifications.cs"), "OptionSettingEnabledSimple", final row placement per UX pass.

**STBL:**
- "Oniki.KinkyMod.OptionSettings.NPCsDisposalNotification"

**Migration:** add-if-missing for keys introduced in this wave (e.g. "PreviousVersion < 446" / "EnsureReleaseRequiredSettingsPresent" alignment with project policy).

**Known limitation (phase 1):** disabling the engine does **not** necessarily block every **on-demand** "NPC.Get" / create / recreate caller outside startup/dispose; some feature paths may still instantiate premades, but the **dispose-notification spam path** is suppressed when OFF + notify toggle allows.

**Related UX:** Services rows "NPCDisableCelebrity", "EnableKWPremadeNPCs", "NPCEnableOnAllWorlds" use **Enabled/Disabled** display normalization ("OptionSettingEnabledSimple"); Dialogs menu uses the same for "DialogBoxTogglePause" and "NPCsDisposalNotification".

Build 446 v089 - Performance / load telemetry (non-gameplay)

**Principles:** preserve full KW functional compatibility; reduce load/runtime overhead where safe; favor evidence-driven, low-risk patches with easy rollback; long-session stability over speculative broad rewrites.

**"WooHooStage" loader:** **modern-first** package resolution with **legacy fallback** (legacy-first removed to avoid unnecessary double work); legacy compatibility retained.

**Load / save-load profiling channel (unified):**
- All load-time probes route through **"KW_LoadingTimeDebug_"** (export file family).
- Toggle: **"LogKWSaveLoadProfile"** (Miscellaneous → Logging Features), default **off** (unchanged).
- Output still gated by **"EnableGlobalBuffer"** (master runtime buffer): when global buffer is off, buffered diagnostic channels do not accumulate.
- Investigative extra XML channels **removed** from shipping flow: "KW_WooHooStageLoadAudit_*", "KW_PreLoadDiagnostics_*" (superseded by the unified channel).
- Dump policy: load-profile channel may flush on **world quit** as well, with coherent clear when the toggle and/or global buffer are disabled.

**Logging hot-path hardening:** guards before expensive string build/concat on hot paths so when logging is off, overhead stays minimal; full detail unchanged when logging is on (project policy: expand guards incrementally).

**Micro-optimizations (2026-04-21 wave - no gameplay behavior change):**
- **"AssemblySearch":** per-assembly **"Type[]" cache** to cut repeated reflection scans in "Search<T>()" / "Search<T>(methodName)".
- **"NakedBroadcaster.OnStay":** single "SimData.Get" reuse (removed duplicate lookup).
- **"NakedBroadcaster.GetMoodScore":** avoid copying spectators into a new "List<Sim>" where an enumerable pass suffices.
- **"Scoring":** internal overload reusing known "SimData" for the naked Sim; fewer repeated lookups in spectator loops.
- **"SimData.UpdateCooldowns" / "UpdateBroadcasters":** reusable **scratch lists** per instance instead of per-tick/per-update "new List<...>" allocations; defensive scratch init on load fix-up.
- **"Log":** non-exception channel append uses "StringBuilder"-style chaining instead of "text + "\r"" concatenation where touched.

**Explicitly deferred (not in this pass):** non-critical post-load deferral of "BookKinkyData.AddToBookStores" / bookshelf scan - evaluated, **not** applied pending stronger A/B telemetry (risk minimization).

**Diagnostics context (from analysis):** "OnPreLoad" + world/save load remain the main KW-tunable load window; "IPreLoad" / "IEnumPatcher" mass not the dominant outlier in sampled runs; large modern WooHoo stage packages dominate stage-load cost - informs future package/split work, not a single bootstrap “smoking gun.”

Build 446 v089 - Physical attractiveness, Seduce, and WooHoo outcome scoring (context)

**Shipped in this build (attractiveness-adjacent):** see the opening section **“Seduce minimum attractiveness bypass”** - setting "SeduceIsFreeToUse" (default **on** in "ResetSettings()" / migration; preserve serialized values when present). When enabled, "Seduce" skips only the legacy gate requiring actor "Attractive >= 6" (or Attractive trait/moodlet); all other Seduce logic unchanged. STBL: "Oniki.KinkyMod.OptionSettings.SeduceIsFreeToUse" / "0x227AF22F80E167F6"

**Code-verified behavior (documentation, not a behavior change in this wave):**
- WooHoo session outcomes are aggregated in "WooHooInstance" / "WooHooTools.PostWooHoo" / "PostWooHoo_Effects" from mastery, mood, random, stage/time multipliers, category bonuses, etc.
- **Physical attraction** is heavily used in initiation/autonomy/scoring paths but is **not** a dominant explicit term in the **final post-session outcome score** in the current formula - which can feel decoupled from “how good the WooHoo was” in UI text.
- Setting **"KWWooHooOutcome"**: when **on**, computed score path; when **off**, legacy fixed-style fallback - any future outcome redesign must keep this contract.

**Not shipped (design backlog in project notes):** optional weighted outcome profile ("OutcomeUsesAttractionWeight", partner contribution, "OutcomeRedesignProfile" Legacy/Balanced/Expressive), threshold smoothing, telemetry decomposition logs - incremental, toggle-gated work only when explicitly scheduled.

Build 446 v089 - Hygiene / outfit plumbing parity when "Main.Settings.Enabled" is off

**Problem:** "SimData.Get(SimDescription)" (single-arg) returns **null** when KW is disabled, while the DLL still replaces several interaction singletons. Shower/bath/sponge/change-clothes then mixed vanilla "base.Run" with KW definitions or lost "SwitchOutfitHelper" / censor paths - inconsistent outfit and mosaic behavior.

**Plumbing layer:** use **"SimData.Get(SimDescription, false)"** (and constructors that already use it) so "OutfitManager", spin, and outfit resolution still work with KW globally off; **"base.Run()" / "OldSingleton" fallback** only when that plumbing "simData" is **null**.

**"TakeShower":** unified "Run" with "Get(Actor, false)"; censor when "!Enabled || IsUnderAge"; "ClearBatheCensorMosaicIfApplied" after exit; "DuringShower" KW-only bits gated on "Enabled"; crabs tweak **Enabled** only; outdoor/public classes: same "Get(..., false)", no "!Enabled" short-circuit to full vanilla "Run" for everyone.

**"BathTub_TakeBath":** same SimData entry pattern; censor + clear; shave/menstrual/crabs **Enabled** only; **"Cleanup":** "SimTools.DisableCensor(Actor)".

**"Sink_SpongeBath":** **no** early "return base.Run()" on "!Enabled" (that path left "Sim.IDisallowClothingChange" KW definition active and broke naked + censor). Unified "Run" like bath when plumbing "simData" exists; "EnableCensor(FullBody)" when "!Enabled || IsUnderAge"; "ClearBatheCensorMosaicIfApplied" after "StandardExit"; "Cleanup" "DisableCensor"; maxi/gusset/crabs **Enabled** only.

**"ChangeClothes":** "Get(..., false)" for spin with mod off; pre-teen picker blocks (Naked index 0, SkinnyDippingTowel, special towel row rules).

**Design notes:** teen mosaic policy follows "IsUnderAge" / Teens setting; KW **auto towel after bath** still requires "Enabled" + "UseTowelAfterBath" + teen+ - not used on the pure “mod off” plumbing path by design.

**Representative sources:** "SimData.cs", "SwitchOutfitHelper.cs", "TakeShower.cs", "BathTub_TakeBath.cs", "Sink_SpongeBath.cs", "ChangeClothes.cs", "ShowerOutdoor_TakeShower.cs", "ShowerPublic_Dance_TakeShower.cs", "SimTools.cs" (censor helpers).

Build 446 v089 - Unified kinky traits read path (post-445 migration parity)

**Root cause (Build 445+ architecture):** migratable kinky traits are owned by **"KinkyTraitsState"** with persistence on **"SimData"**; **"SyncToTraitManager"** strips those GUIDs from the **vanilla "TraitManager"**. Any gameplay code that still used "sim.HasTrait(kinkyGuid)" or "TraitManager.HasElement" / "HasAnyElement" for **"Traits.IsMigrationEligibleKinky(guid)"** could read **false** after migration even when the Sim **has** the trait in unified state - perceived as broken **Slut / Exhibitionist** gates, strip autonomy, WooHoo branches, etc.

**Source of truth (new rule for code and future edits):**
- **Kinky + migratable** ("Traits.IsMigrationEligibleKinky"): use **"SimTools.HasTraitUnified"** / **"HasKinkyTrait"** / **"HasAnyTraitUnified"** (or existing wrappers like "IsExhibitionist", "IsMasochist", "IsIncestuous") - **do not** use vanilla "HasTrait" / "TraitManager" for the same kinky GUID after migration.
- **Pure vanilla EA traits:** "TraitManager.HasElement" / "sim.HasTrait" remain correct.
- **“Any of these kinky IDs”:** "HasAnyTraitUnified" / "HasAnyKinkyTrait" depending on whether vanilla+kinky or unified-only is intended.
- Canonical GUIDs live in **"Oniki.Gameplay.Traits"** ("Traits.*", "AllTraits"); do not duplicate hex lists in consumer files.

**Refactor scope (batched grep-driven migration, build-verified):** replace vanilla kinky reads across interactions and gameplay so behavior matches **pre-unification** intent while reading **unified** storage. High-level coverage (see "DONE - Unified Traits Patch" for the full per-file checklist):

- **Exhibition / Slut chain:** "ShowBoobs", "ShowBra", "ShowBottom", "ShowPanties", "ShowFeet", "Tease", "Seduce", "AskToShow*" family, related Dominant / "WooHooLess" gates.
- **WooHoo core:** "WooHooSequence", "WooHooInstance", "WooHooTools", "WooHooLoop", "WooHooSkill", "Grope", "InviteToWooHoo", sleeping/couch variants, bribe/recruit flows, generic watch/peep, hole-in-wall, etc.
- **Life / objects / hygiene:** "WatchTV", "BathTub_TakeBath", "Phone_TakeSelfie", "UseToilet", "Book_ReadBook", "Terrain_MopPuddle", "KWBedSleep", undress/recruit/whore-adjacent interactions.
- **Data / relations / autonomy:** "SimData", "Scoring", "NakedBroadcaster", "WooHooBroadcaster", "Jealousy", "SimRelation", legacy "Autonomy.cs", "Womb", "LotTools", "KWActiveTopicData", "RapeBroadcaster", situations ("WooHooParty", "HighSchoolSituation", "Callgirl*"), "Spank", "Janitor", "SocialCallbacks" (e.g. Rapist boast path).
- **Pie / commodity bridge:** "KWInteraction" - kinky commodity "TraitManager.GetElement" paths aligned with **unified** read + reward fallback where applicable.

**"SocialCallbacks":** exhibition / learn-trait paths verified to use **"HasKinkyTrait" / "HasAnyKinkyTrait" / "HasTraitUnified"** rather than stale "TraitManager" reads on migrated GUIDs.

**Migration / save:** world migration remains driven by existing unified-trait migration entry points (e.g. "Main.RunUnifiedKinkyTraitsMigrationIfNeeded", flag **"UnifiedKinkyTraitsMigration445Done"** in settings integrity); this patch is **read-path** alignment, not a second trait storage model.

**Non-goals:** no duplicate trait state (no re-adding kinky traits to "TraitManager" as a workaround); no broad cosmetic refactors outside touched callsites.

**Validation posture:** saves that already ran unified migration should recover **Show*** / Slut / WooHoo branch parity; new Sims via KW trait UI still use "AddTraitUnified"; NPC baseline/spread remains read-consistent. Smoke: "WooHooJoinInShower", autonomous phone photo flows as regression spot-checks.

Build 446 v089 - Change outfit restyling ("SwitchOutfitHelper" + "ChangeClothes")

**Goal:** one consistent **SimData / OutfitManager** path for spin-based outfit changes (bathe, sleep, WooHoo, toilet, groping/show interactions, showers/sponge) and for the **Change Clothes** pie flow, including when **"Main.Settings.Enabled" is false** but plumbing "SimData" exists ("Get(SimDescription, false)").

**"SwitchOutfitHelper" highlights:**
- Constructors resolve **"SimData.Get(owner.SimDescription, false)"** → bind to **"simData.OutfitManager"**; invalid/missing plumbing handled in "Initialize" (callers already guard "Start" / lifecycle).
- **Nudist lot:** forces bath-style **full naked** policy.
- **"IsFullUndress":** mirrors **upper/lower naked** flags for coherent full undress.
- **"Setup":** recovers invalid **current outfit** via description + **"OutfitTools.GetValidOutfit"** before spin.
- **"GettingOutOfBath":** optional **EP3 towel** after bath - gated on **"UseTowelAfterBath"**, **"Main.Settings.Enabled"**, age/home/pregnancy/toddler rules, teen **CASP existence** probes; **"TryCreateTowelOutfit"** tries **multiple EP3 outfit name orderings** (legacy packs); caches/repairs **"SpecialOutfit("towel")"**.
- **Situations:** Everyday routing can map to **HighSchool / Whore / Callgirl** uniform indices inside **"GetCategoryAndIndex"**.
- **"PostLoad":** restarts helper after load when previously started (debug log on failure).

**"ChangeClothes" (Oniki "Definition" + "IPreLoad"):**
- **"Test":** robot gate uses **"SimData.Get(..., false)"** + **"IsDroid"**.
- **"Run":** primary path **"OutfitManager.SwitchToOutfit" / "SwitchToUniform"**; optional **"UseVanillaLikeUserDirectedOutfitPromotion"** with **"TryApplyUserDirectedDefaultPromotion"** - failure **auto-disables** promotion setting + user notify; vanilla **delayed CAS** fallback when no "OutfitManager".
- **Pre-teen safety:** removes **Naked index 0**, **SkinnyDippingTowel**, and **special towel** rows from picker; blocks **"PushInteraction"** for those keys.
- Picker adds **KW uniform** tab entries from **"OutfitManager.GetUniforms()"** when present.

**Representative consumers:** "TakeShower", outdoor/public shower classes, "BathTub_TakeBath", "Sink_SpongeBath", "KWBedSleep", "MasturbateOnSleepingSimOnBed", "UseToilet", "WooHooInstance", "Grope", "ShowBottom", "ShowFeet", etc. (full grep-backed list in **"DONE - ChangeOutfit restyling"**).